package com.xxjh.brs.ftp.service;

import com.xxjh.brs.ftp.common.api.enums.ResponseStatus;
import com.xxjh.brs.ftp.common.exceptions.ConditionException;
import com.xxjh.brs.ftp.common.predicate.FtpPredicate;
import com.xxjh.brs.ftp.common.utils.FileUtil;
import com.xxjh.brs.ftp.common.utils.FtpFileSearchUtil;
import com.xxjh.brs.ftp.common.utils.FtpFileUtil;
import com.xxjh.brs.ftp.vo.FileVo;
import com.xxjh.brs.ftp.vo.FloderVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.pool2.ObjectPool;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author CHENCHEN
 * @title 抽象FTP基础服务方法
 * @company 新兴际华科技（天津）有限公司
 * @create 2024-07-03 19:21
 */
@Slf4j
@Component
@Scope("prototype")
public abstract class AbstractFtpService {

    @Value("${spring.ftp.localdownloaddir}")
    protected String localDownloadDir;

    protected String ftpParentPath;

    protected FTPClient ftpClient;

    // 抽象方法，由子类实现以提供具体的ObjectPool实例
    protected abstract ObjectPool<FTPClient> getFtpClientPool();

    // 获取ftpClient客户端，现在是抽象方法的调用
    protected void getFTPClient() {
        try {
            ftpClient = getFtpClientPool().borrowObject();
            ftpParentPath = ftpClient.printWorkingDirectory() + "/";
        } catch (Exception e) {
            e.printStackTrace();
            throw new ConditionException(ResponseStatus.FTP_GET_CLIENT_EXCEPTION);
        }
    }

    /**
     * 查找FTP服务器指定文件夹下是否存在指定文件并返回路径。
     *
     * @param drictoryPath FTP目标服务器根路径下具体某文件夹路径。
     * @param specifiedLayer 有值代表指定目录，无值检索全部。
     * @param ftpPredicate    断言，匹配满足需求的文件及目录。
     * @return FTP服务器现所有指定规则文件存放路径。
     */
    protected List<FileVo> searcherFtpCustomizedDirectory(String drictoryPath, String specifiedLayer, FtpPredicate ftpPredicate){
        List<FileVo> fileVos;
        try {
            fileVos = searchCustomizedDirectory(ftpParentPath + drictoryPath, specifiedLayer, ftpPredicate)
                    .stream().map(s -> new FileVo(s))
                    .collect(Collectors.toList());
        } catch (IOException e) {
            throw new ConditionException(ResponseStatus.FTP_FIND_FILE_EXCEPTION);
        }
        return fileVos;
    }

    /**
     * FTP获取资源服务器指定文件夹最后更新时间
     *
     * @param drictoryPath 指定路径。
     * @param specifiedLayer 文件夹名称。
     * @return 当前文件夹属性类。
     */
    protected FloderVo lastUpdateFloderProperties(String drictoryPath, String specifiedLayer){
        try {
            return searchLastUpdateDirectory(drictoryPath + specifiedLayer);
        } catch (IOException e) {
            throw new ConditionException(ResponseStatus.FTP_FIND_LAST_UPDATE_DIRECTORY_EXCEPTION);
        }
    }

    /**
     * 删除FTP指定文件目录及下面文件。
     *
     * @param rootPath 指定路径。
     * @param directoryPath 有值代表指定目录，无值检索全部。
     * @return 删除成功或失败。
     */
    public boolean deleteLocalDirectory(String rootPath, String directoryPath){
        try {
            //删除本地目录
            return FileUtil.deleteFolderContents(new File(localDownloadDir + rootPath + directoryPath));
        } catch (Exception e) {
            throw new ConditionException(ResponseStatus.FILE_LOCAL_DEL_EXCEPTION);
        }
    }

    /**
     * 删除FTP服务器指定目录及其所有内容（包括子目录和文件）。
     *
     * @param directoryPath 要删除的目录的完整路径。
     * @return 如果目录及其内容成功删除则返回true，否则返回false。
     * @throws IOException 如果发生I/O错误或FTP操作失败时抛出。
     */
    protected boolean deleteDirectoryRecursively(String directoryPath) {
        try {
            // 改变当前工作目录到要删除的目录
            if (!ftpClient.changeWorkingDirectory(directoryPath)) {
                return true;
            }
            log.info("当前所在目录: {}", ftpClient.printWorkingDirectory());
            log.info("需要删除的目录: {}", directoryPath);
            FTPFile[] files = ftpClient.listFiles(directoryPath);
            for (FTPFile file : files) {
                String remoteFilePath = FileUtil.getRemoteFilePath(directoryPath, file.getName());
                //是否为目录
                if (file.isDirectory()) {
                    deleteDirectoryRecursively(remoteFilePath);
                }else{
                    // 如果是文件，则直接删除
                    if (!ftpClient.deleteFile(remoteFilePath)) {
                        return false; // 如果删除文件失败，则立即返回false
                    }
                    log.info("删除成功: {}", remoteFilePath);
                }
            }
            // 最后，删除当前空目录
            if (!ftpClient.removeDirectory(directoryPath)) {
                return false;
            }
            log.info("删除成功: {}", directoryPath);
            return true;
        }catch (Exception e) {
            throw new ConditionException(ResponseStatus.FTP_DELETE_DIRECTORY_EXCEPTION);
        }
    }

    /**
     * 通过FTP下载文件到本地。
     *
     * @param specifiedLayer 有值代表指定目录，无值检索全部。
     * @param existKey 跳过指定目录。
     *
     */
    protected void downloadFtp(String drictoryPath, String specifiedLayer, List<String> existKey){
        try {
            downloadDirectory(ftpParentPath, localDownloadDir + drictoryPath + specifiedLayer, existKey);
        } catch (IOException e) {
            throw new ConditionException(ResponseStatus.FTP_DOWNLOADING_EXCEPTION);
        }
    }

    /**
     * 本地文件上传到FTP服务器。
     * 支持多级目录文件
     *
     * @param drictoryPath FTP目标服务器根路径下具体某文件夹路径。
     * @param specifiedLayer 代表指定目录。
     *
     */
    protected void uploadLocalFtp(String drictoryPath, String specifiedLayer){
        try {
            uploadDirectory(drictoryPath + specifiedLayer, localDownloadDir + specifiedLayer);
        } catch (IOException e) {
            throw new ConditionException(ResponseStatus.FTP_UPLOADING_EXCEPTION);
        }

    }

    /**
     * 通过FTP下载文件到本地目录。
     *
     * @param remoteDirPath FTP需要查找到目录，首次调用传入根路径
     * @param localDirPath 存放本地目录
     * @param existDirectory 已同步过地图目录
     * @return
     */
    private void downloadDirectory(String remoteDirPath, String localDirPath, List<String> existDirectory) throws IOException {
        log.info("ftpClient 是否打开: {}", ftpClient);
        if(ftpClient != null){
            Files.createDirectories(Paths.get(localDirPath));
            log.info("文件路径: {}", remoteDirPath);
            FTPFile[] files = ftpClient.listFiles(remoteDirPath);
            for (FTPFile file : files) {
                if (Thread.currentThread().isInterrupted()) {
                    log.warn("定时任务被提前结束, 中断FTP下载3D瓦片到本地目录");
                    return; // 中断标记已设，提前结束循环
                }
                if(existDirectory != null &&existDirectory.contains(file.getName())){
                    continue;
                }
                //拼接文件路径
                String remoteFilePath = FileUtil.getRemoteFilePath(remoteDirPath, file.getName());
                log.info("文件路径: {}", remoteFilePath);
                String localFilePath = Paths.get(localDirPath, file.getName()).toString();
                //是否为目录
                if (file.isDirectory()) {
                    Files.createDirectories(Paths.get(localFilePath));
                    downloadDirectory(remoteFilePath, localFilePath, existDirectory);
                } else {
                    File localFile = new File(localFilePath);
                    // 检查文件是否已存在（在下载操作之前）
                    if (localFile.exists()) {
                        log.info("本地文件已存在，跳过下载: {}", localFilePath);
                        continue; // 跳过当前文件的下载
                    }
                    try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(localFile))) {
                        log.info("Downloading file: {} to {}", remoteFilePath, localFilePath);
                        boolean success = ftpClient.retrieveFile(remoteFilePath, outputStream);
                        if (!success) {
                            throw new IOException("Failed to download file: " + remoteFilePath);
                        }
                        log.info("Downloaded: {} to {}", remoteFilePath, localFilePath);
                    }
                }
            }
        }
    }

    /**
     * 递归上传指定本地目录到远程服务器目录。
     *
     * @param remoteDirPath 远程目录路径(所有文件上传的统一前缀)
     * @param localParentDir 本地文件路径(上传的本地目录)
     * @throws IOException 如果文件操作失败则抛出异常
     */
    protected void uploadDirectory(String remoteDirPath, String localParentDir) throws IOException {
        localParentDir = Paths.get(localParentDir).toString();
        File localDir = new File(localParentDir);
        String[] subFiles = localDir.list();
        if (subFiles != null && subFiles.length > 0) {
            for (String item : subFiles) {
                if (Thread.currentThread().isInterrupted()) {
                    log.warn("定时任务被提前结束, 中断递归上传指定本地目录到FTP资源服务器");
                    return; // 中断标记已设，提前结束循环
                }
                File localFile = new File(localParentDir, item);
                if (localFile.isFile()) {
                    uploadFileToFtp(localFile, item);
                } else {
                    String remoteSubDir = remoteDirPath + "/" + item;
                    mkdirs(remoteSubDir);
                    uploadDirectory(remoteSubDir, localFile.getAbsolutePath());
                    // 递归调用后返回到之前的目录
                    ftpClient.changeToParentDirectory();
                }
            }
        }
    }

    /**
     * 上传单个文件到远程服务器。
     *
     * @param localFile     本地文件对象
     * @param remoteFilePath 远程文件路径
     * @throws IOException 如果文件传输失败则抛出异常
     */
    protected void uploadFileToFtp(File localFile, String remoteFilePath) throws IOException {
        if (Thread.currentThread().isInterrupted()) {
            log.warn("定时任务被提前结束, 中断上传单个文件到远程服务器");
            return; // 中断标记已设，提前结束循环
        }
        boolean skipUpload = false;
        // 检查远程路径下是否已存在同名文件
        FTPFile[] existingFiles = ftpClient.listFiles(new File(remoteFilePath).getParent());
        if (existingFiles != null) {
            for (FTPFile ftpFile : existingFiles) {
                if (ftpFile.getName().equals(localFile.getName())) {
                    // 文件已存在，标记为跳过上传
                    skipUpload = true;
                    log.info("文件 [{}] 已存在于远程路径 [{}], 跳过上传", localFile.getName(), remoteFilePath);
                    break;
                }
            }
        }
        if (!skipUpload) {
            try (FileInputStream inputStream = new FileInputStream(localFile)) {
                boolean b = ftpClient.storeFile(remoteFilePath, inputStream);
                if (!b) {
                    log.info("上传本地文件 {} 到FTP服务器出现错误", localFile.getAbsolutePath());
                }
            }
        }
    }

    /**
     * FTP服务器创建文件夹
     *
     * @param remoteDir 创建文件夹名称
     * @throws IOException 如果文件夹创建失败则抛出异常
     */
    protected void mkdirs(String remoteDir) throws IOException {
        ftpClient.changeWorkingDirectory(this.ftpParentPath);
        String[] dirs = remoteDir.split("/");
        for (String dir : dirs) {
            if (dir.isEmpty()) continue;
            if (!ftpClient.changeWorkingDirectory(dir)) {
                log.info("当前目录[{}] ,当前需要创建的目录[{}]", ftpClient.printWorkingDirectory(), dir);
                ftpClient.makeDirectory(dir);
                log.info("创建完毕，当前目录[{}] ,当前需要创建的目录[{}]", ftpClient.printWorkingDirectory(), dir);
                ftpClient.changeWorkingDirectory(dir);
                log.info("创建成功: [{}], 当前目录[{}]", remoteDir, ftpClient.printWorkingDirectory());
            }
        }
    }

    /**
     * 主要方法，用于通过FTP客户端在指定的FTP根目录及其子目录下查找文件路径。
     * 支持指定起始层以限制搜索范围，并会根据文件类型优先级进行筛选。
     *
     * @param ftpRootDir  FTP服务器上的根目录路径。
     * @param specifiedLayer 可选的起始子目录名称，用于限定搜索起始点。
     * @param ftpPredicate    断言，匹配满足需求的文件及目录。
     * @return 符合条件文件路径列表，按优先级排序。
     * @throws IOException 在与FTP服务器交互过程中可能发生的I/O异常。
     */
    private List<String> searchCustomizedDirectory(String ftpRootDir, String specifiedLayer, FtpPredicate ftpPredicate) throws IOException {
        if (ftpClient != null) {
            List<String> tilesetPaths = new ArrayList<>();
            String startDirectory = ftpRootDir + (specifiedLayer != null && !specifiedLayer.isEmpty() ? specifiedLayer + "/" : "");
            log.info("开始搜索目录查找指定文件 [{}]", startDirectory);
            List<String> directoriesToSearch = new ArrayList<>();
            directoriesToSearch.add(startDirectory);
            searchFtpDirectories(directoriesToSearch, tilesetPaths, ftpPredicate);
            return FtpFileSearchUtil.pathFilter(tilesetPaths, ftpPredicate);
        }
        return null;
    }

    /**
     * 递归遍历FTP目录，查找指定目录的指定文件。
     *
     * @param startDirectory  待搜索的目录路径列表，作为递归起始。
     * @param tilesetPaths    用于收集找到的瓦片集文件路径。
     * @param ftpPredicate    断言，匹配满足需求的文件及目录。
     * @throws IOException 在文件操作中可能发生的I/O异常。
     */
    private void searchFtpDirectories(List<String> startDirectory, List<String> tilesetPaths, FtpPredicate ftpPredicate) throws IOException {
        List<String> nextDirs = new ArrayList<>();
        for (String remoteDirPath : startDirectory) {
            FTPFile[] files = ftpClient.listFiles(remoteDirPath);
            for (FTPFile file : files) {
                if (Thread.currentThread().isInterrupted()) {
                    log.warn("定时任务被提前结束, 中断递归遍历FTP目录查找3D瓦片集文件");
                    return;
                }
                if (file.isDirectory()) {
                    String remoteFilePath = FileUtil.getRemoteFilePath(remoteDirPath, file.getName());
                    FTPFile[] nextFiles = ftpClient.listFiles(remoteFilePath);
                    Optional<String> foundPath = FtpFileUtil.findFileInFtpDirectory(nextFiles, remoteFilePath, ftpPredicate);
                    foundPath.ifPresent(tilesetPaths::add);
                    if (!foundPath.isPresent()) {
                        nextDirs.add(remoteFilePath);
                    }
                }
            }
        }
        if (!nextDirs.isEmpty()) {
            searchFtpDirectories(nextDirs, tilesetPaths, ftpPredicate);
        }
    }

    /**
     * 查找指定目录下最后一个修改的文件目录。
     *
     * @param remoteDirectory  指定FTP路径下文件目录。
     * @return 最后一个修改的文件目录名称和修改时间。
     * @throws IOException 在文件操作中可能发生的I/O异常。
     */
    private FloderVo searchLastUpdateDirectory(String remoteDirectory) throws IOException {
        FTPFile[] directories = ftpClient.listFiles(remoteDirectory, FTPFile::isDirectory);
        if (directories.length > 0) {
            FTPFile newestDirectory = FtpFileUtil.findNewestDirectory(directories);
            if (newestDirectory != null) {
                log.info("getTimeZone() =====> {}", newestDirectory.getTimestamp().getTimeZone());
                log.info("getTimeInMillis() =====> {}", newestDirectory.getTimestamp().getTimeInMillis());
                log.info("getTime().getTime() =====> {}", newestDirectory.getTimestamp().getTime().getTime());
                log.info("找到最后一个修改文件 [{}], 上次修改时间是 [{}]", newestDirectory.getName(), newestDirectory.getTimestamp().getTime().getTime());
                return new FloderVo(newestDirectory.getName(), newestDirectory.getTimestamp().getTime().getTime());
            } else {
                log.info("没有找到目录。");
            }
        } else {
            log.info("指定目录下没有子目录。");
        }
        return null;
    }
}
